#include "common.h"
#include "errno.h"
#include "linux/sched.h"
#include "linux/kernel.h"

#include "blk.h"

//The request-struct contains all necessary data to load a nr of sectors into memory
struct request request[NR_REQUEST];

//used to wait on when there are no free requests
struct task_struct *wait_for_request = NULL;

//blk_dev_struct is:
//  do_request-address
//  next-request

struct blk_dev_struct blk_dev[NR_BLK_DEV] = 
{
    {NULL,NULL},//no_dev
    {NULL,NULL},//dev mem
    {NULL,NULL},//dev fd(unused)
    {NULL,NULL},//dev hd(unused)
    {NULL,NULL},//dev ttyx
    {NULL,NULL},//dev tty(unused)
    {NULL,NULL}//dev lp
};

static inline void lock_buffer(struct buffer_head *bh)
{
    sysctl_disable_irq();

    while(bh -> b_lock)
    {
        sleep_on(&bh -> b_wait);
    }

    bh -> b_lock = 1;
    sysctl_enable_irq();
}

static inline void unlock_buffer(struct buffer_head *bh)
{
    if(!bh -> b_lock)
    {
        printk("ll_rw_block.c: buffer not locked\r\n");
    }

    bh -> b_lock = 0;
    wake_up(&bh -> b_wait);
}

//add-request adds a request to the linked list.
//It disables interrupts so that it can muck with the request-lists in peace.
static void add_request(struct blk_dev_struct *dev,struct request *req)
{
    struct request *tmp;

    req -> next = NULL;
    sysctl_disable_irq();

    if(req -> bh)
    {
        req -> bh -> b_dirt = 0;
    }

    if(!(tmp = dev -> current_request))
    {
        dev -> current_request = req;
        sysctl_enable_irq();
        (dev -> request_fn)();
        return;
    }

    for(;tmp -> next;tmp = tmp -> next)
    {
        if((IN_ORDER(tmp,req) || !IN_ORDER(tmp,tmp -> next)) && IN_ORDER(req,tmp -> next))
        {
            break;
        }
    }

    req -> next = tmp -> next;
    tmp -> next = req;
    sysctl_enable_irq();
}

static void make_request(int major,int rw,struct buffer_head *bh)
{
    struct request *req;
    int rw_ahead;

    //WRITEA/READA is special case - it is not really needed,so if the
    //buffer is locked,we just forget about it,else it's a normal read
    if((rw_ahead = ((rw == READA) || (rw == WRITEA))))
    {
        if(bh -> b_lock)
        {
            return;
        }

        if(rw == READA)
        {
            rw = READ;
        }
        else
        {
            rw = WRITE;
        }
    }

    if((rw != READ) && (rw != WRITE))
    {
        panic("Bad block dev command,must be R/W/RA/WA");
    }

    lock_buffer(bh);

    if(((rw == WRITE) && (!bh -> b_dirt)) || ((rw == READ) && (bh -> b_uptodate)))
    {
        unlock_buffer(bh);
        return;
    }

    repeat:
        //we don't allow the write-requests to fill up the queue completely:
        //we want some room for reads: they take precedence.The last third
        //of the requests are only for reads

        if(rw == READ)
        {
            req = request + NR_REQUEST;
        }
        else
        {
            req = request + ((NR_REQUEST * 2) / 3);
        }

        //find an empty request
        while(--req >= request)
        {
            if(req -> dev < 0)
            {
                break;
            }

            //if none found,sleep on new requests: check for rw_ahead
            if(req < request)
            {
                if(rw_ahead)
                {
                    unlock_buffer(bh);
                    return;
                }

                sleep_on(&wait_for_request);
                goto repeat;
            }
        }

        //fill up the request-info,and add it to the queue
        req -> dev = bh -> b_dev;
        req -> cmd = rw;
        req -> errors = 0;
        req -> sector = bh -> b_blocknr << 1;
        req -> nr_sectors = 2;
        req -> buffer = bh -> b_data;
        req -> waiting = NULL;
        req -> bh = bh;
        req -> next = NULL;
        add_request(major + blk_dev,req);
}

void ll_rw_block(int rw,struct buffer_head *bh)
{
    uint major;

    if(((major = MAJOR(bh -> b_dev)) >= NR_BLK_DEV) || (!blk_dev[major].request_fn))
    {
        printk("Trying to read nonexistent block-device\r\n");
        return;
    }

    make_request(major,rw,bh);
}

void blk_dev_init()
{
    int i;

    for(i = 0;i < NR_REQUEST;i++)
    {
        request[i].dev = -1;
        request[i].next = NULL;
    }
}